use std::any::{Any};
use std::rc::{Rc};
use tree;
use frag;
use geom;
use sdl2::pixels::{Color};

////////////////////////////////////////////////////////////////////////////////////////////////////
// GUI type

/// Represents some GUI element to render onto the screen.
#[derive(Clone, Eq, PartialEq)]
#[repr(transparent)]
pub struct GUI(tree::Virt<frag::Frag, DynComponent>);

impl From<frag::Frag> for GUI {
  fn from(frg: frag::Frag) -> Self {
    GUI(tree::Virt::frag(frg))
  }
}

/// Creates a GUI element equivalent to `frag::empty()`.
pub fn empty() -> GUI {
  frag::empty().into()
}

/// Creates a GUI element equivalent to `frag::fill(color)`.
pub fn fill(color: Color) -> GUI {
  frag::fill(color).into()
}

/// Creates a GUI element equivalent to `frag::rect(dim, color)`.
pub fn rect(dim: geom::Dim, color: Color) -> GUI {
  frag::rect(dim, color).into()
}

fn with_spine_info(spine: frag::FragSpine, info: frag::FragInfo, guis: Vec<GUI>) -> GUI {
  use std::mem::{transmute};
  GUI(tree::Virt::branch(spine, info, unsafe {
    transmute(guis) // safe, because GUI is repr(transparent)
  }))
}

fn with_spine(spine: frag::FragSpine, guis: Vec<GUI>) -> GUI {
  with_spine_info(spine, frag::FragInfo::default(), guis)
}

/// Like `frag::pack_lr()` but combines the given GUI elements.
pub fn pack_lr(guis: Vec<GUI>) -> GUI { with_spine(frag::FragSpine::PackLR, guis) }

/// Like `frag::pack_rl()` but combines the given GUI elements.
pub fn pack_rl(guis: Vec<GUI>) -> GUI { with_spine(frag::FragSpine::PackRL, guis) }

/// Like `frag::pack_tb()` but combines the given GUI elements.
pub fn pack_tb(guis: Vec<GUI>) -> GUI { with_spine(frag::FragSpine::PackTB, guis) }

/// Like `frag::pack_bt()` but combines the given GUI elements.
pub fn pack_bt(guis: Vec<GUI>) -> GUI { with_spine(frag::FragSpine::PackBT, guis) }

/// Like `frag::overlay()` but combines the given GUI elements.
pub fn overlay(guis: Vec<GUI>) -> GUI { with_spine(frag::FragSpine::Overlay, guis) }

/// Like `frag::container()` but combines the given GUI elements.
pub fn container(guis: Vec<GUI>) -> GUI { with_spine(frag::FragSpine::Container, guis) }

impl GUI {

  pub fn container(self) -> GUI { container(vec![self]) }
  pub fn overlay(self, oth: GUI) -> GUI { overlay(vec![self, oth]) }
  pub fn underlay(self, oth: GUI) -> GUI { overlay(vec![oth, self]) }

  pub fn bounds(mut self, bounds: geom::Dim) -> GUI {
    self.0.info_mut().map(|i| i.bounds = bounds);
    self
  }

  pub fn width(mut self, width: geom::Len) -> GUI {
    self.0.info_mut().map(|i| i.bounds.0 = width);
    self
  }

  pub fn height(mut self, height: geom::Len) -> GUI {
    self.0.info_mut().map(|i| i.bounds.1 = height);
    self
  }

  pub fn align(mut self, align_h: frag::AlignH, align_v: frag::AlignV) -> GUI {
    self.0.info_mut().map(|i| { i.align_h = align_h; i.align_v = align_v; });
    self
  }

  pub fn align_h(mut self, align_h: frag::AlignH) -> GUI {
    self.0.info_mut().map(|i| i.align_h = align_h);
    self
  }

  pub fn align_v(mut self, align_v: frag::AlignV) -> GUI {
    self.0.info_mut().map(|i| i.align_v = align_v);
    self
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Component trait

/// Trait for user-definable GUI components.
pub trait Component: Sized + PartialEq + 'static {
  type State: PartialEq;

  /// Generate an initial state for this component.
  fn init(&self) -> Self::State;

  /// Render this component when it has the given state.
  fn render(&self, state: &Self::State) -> GUI;

  /// Return `true` if the next configuration/state indicates that the component should be updated
  /// and rerendered.
  fn should_update(&self,
                   s: &Self::State,
                   next: &Self,
                   next_s: &Self::State) -> bool {
    *self != *next || *s != *next_s
  }

  /// Create a GUI element for this component.
  fn into_gui(self) -> GUI {
    GUI(tree::Virt::abs(DynComponent(Rc::new(self))))
  }
}

// pure component

#[derive(PartialEq)]
#[repr(transparent)]
struct PureComp<T>(T);

pub trait PureComponent: Sized + Eq + 'static {
  // Render this pure component.
  fn render(&self) -> GUI;

  /// Create a GUI element for this pure component.
  fn into_gui(self) -> GUI { PureComp(self).into_gui() }
}

impl<T> Component for PureComp<T> where T: PureComponent {
  type State = ();
  fn init(&self) {}
  fn render(&self, _: &()) -> GUI { self.0.render() }
  fn should_update(&self, _: &(), next: &Self, _: &()) -> bool { self.0 != next.0 }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// DynComponent: Type-erased `Component`

/// A dynamically-typed component. This is used as the "Abstract" node type in a GUI tree.
#[derive(Clone)]
#[repr(transparent)]
struct DynComponent(Rc<dyn ComponentAny>);

/// Internal trait used to erase the type information from the `Component` trait, making it
/// object-safe.
trait ComponentAny {
  fn as_any(&self) -> &dyn Any;
  fn init(&self) -> Box<dyn Any>;
  fn render(&self, s: &dyn Any) -> GUI;
  fn compatible(&self, cfg: &dyn Any) -> bool;
  fn eq(&self, oth: &dyn Any) -> bool;
  fn should_update(&self, s: &dyn Any, next: &dyn Any, next_s: &dyn Any) -> bool;
}

impl PartialEq for DynComponent {
  fn eq(&self, rhs: &Self) -> bool {
    self.0.eq(rhs.0.as_any())
  }
}

impl Eq for DynComponent {}

// Auto-promote Component to ComponentAny by erasing or recovering type info.

impl<T> ComponentAny for T where T: Component {
  fn as_any(&self) -> &dyn Any {
    self
  }

  fn init(&self) -> Box<dyn Any> {
    Box::new(T::init(self))
  }

  fn render(&self, s_erased: &dyn Any) -> GUI {
    let s = s_erased.downcast_ref::<T::State>()
      .expect("current state to render() should have correct type");
    T::render(self, s)
  }

  fn compatible(&self, cfg: &dyn Any) -> bool {
    cfg.is::<T>()
  }

  fn eq(&self, oth_erased: &dyn Any) -> bool {
    oth_erased
      .downcast_ref::<T>()
      .map(|oth| *oth == *self)
      .unwrap_or(false)
  }

  fn should_update(&self,
                   s_erased: &dyn Any,
                   next_erased: &dyn Any,
                   next_s_erased: &dyn Any) -> bool {
    let s = s_erased.downcast_ref::<T::State>()
      .expect("prev state to should_update() should have correct type");
    match (next_erased.downcast_ref::<T>(),
           next_s_erased.downcast_ref::<T::State>()) {
      (Some(next), Some(next_s)) =>
        T::should_update(self, s, next, next_s),
      (_, _) =>
        false,
    }
  }
}

// tree::Abstract impl. This is mostly just boilerplate, calling ComponentAny trait functions from a
// DynComponent.

impl tree::Abstract<frag::Frag> for DynComponent {
  type State = Box<dyn Any>;
  fn initial_state(&self) -> Box<dyn Any> {
    self.0.init()
  }
  fn render(&self, s: &Box<dyn Any>) -> tree::Virt<frag::Frag, DynComponent> {
    self.0.render(s.as_ref()).0
  }
  fn compatible(&self, oth: &Self) -> bool {
    self.0.compatible(oth.0.as_any())
  }
  fn should_update(&self, s: &Box<dyn Any>, next: &Self, next_s: &Box<dyn Any>) -> bool {
    self.0.should_update(s.as_ref(), next.0.as_any(), next_s.as_ref())
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Live tree type

pub struct LiveGUI {
  live: tree::Live<frag::Frag, DynComponent>,
  frag: frag::Frag,
  size: geom::Dim,
}

impl LiveGUI {

  pub fn new(size: geom::Dim) -> LiveGUI {
    LiveGUI {
      live: tree::Live::default(),
      frag: frag::Frag::default(),
      size,
    }
  }

  pub fn root_frag(&self) -> &frag::Frag {
    &self.frag
  }

  pub fn set_size(&mut self, size: geom::Dim) {
    self.size = size;
    self.frag.calculate_geometry(size);
  }

  pub fn update(&mut self, gui: GUI) {
    self.live.impose(gui.0, &mut self.frag);
    self.frag.calculate_geometry(self.size);
  }
}
